use crate::file_info::FileInfo;
use anyhow::Result;
use futures_util::StreamExt;
use napi_derive_ohos::napi;
use smb::{
    packets::fscc::{FileAccessMask, FileIdBothDirectoryInformation},
    resource::GetLen,
    ClientConfig, Directory, FileCreateArgs, Resource, UncPath,
};
use std::{str::FromStr, sync::Arc, time::Duration};

#[napi]
pub struct Client {
    server: String,
    user_name: String,
    password: String,
    smb: Option<smb::Client>,
}

#[napi]
impl Client {
    #[napi(constructor)]
    pub fn new(server: String, user_name: String, password: String) -> Self {
        Self {
            server,
            user_name,
            password,
            smb: None,
        }
    }

    #[napi]
    pub async unsafe fn init(&mut self, port: Option<u16>, timeout: Option<u32>) -> Result<()> {
        let mut config = ClientConfig::default();
        config.connection.port = port;
        if let Some(secs) = timeout {
            config.connection.timeout = Some(Duration::from_secs(secs.into()));
        }
        let smb = smb::Client::new(config);
        smb.ipc_connect(&self.server, &self.user_name, self.password.to_string())
            .await?;
        self.smb = Some(smb);
        Ok(())
    }

    #[napi]
    pub async unsafe fn list(&mut self, path: String, pattern: String) -> Result<Vec<FileInfo>> {
        let mut files = vec![];
        if let Some(smb) = &mut self.smb {
            let unc = UncPath::from_str(&path)?;
            smb.share_connect(&unc, &self.user_name, self.password.to_string())
                .await?;
            let resource = smb
                .create_file(
                    &unc,
                    &FileCreateArgs::make_open_existing(
                        FileAccessMask::new().with_generic_read(true),
                    ),
                )
                .await?;
            match resource {
                Resource::File(file) => {
                    let size_kb = file.get_len().await.unwrap_or(0);
                    files.push(FileInfo {
                        is_folder: false,
                        content_length: size_kb as i64,
                        href: file.name().to_string(),
                    });
                }
                Resource::Directory(dir) => {
                    let dir = Arc::new(dir);
                    files.push(FileInfo {
                        is_folder: true,
                        content_length: 0,
                        href: dir.name().to_string(),
                    });
                    let mut info_stream =
                        Directory::query::<FileIdBothDirectoryInformation>(&dir, &pattern).await?;
                    while let Some(info) = info_stream.next().await {
                        if let Ok(file) = info {
                            files.push(FileInfo {
                                is_folder: false,
                                content_length: file.allocation_size as i64,
                                href: file.file_name.to_string(),
                            });
                        }
                    }
                }
                _ => {}
            }
        }
        Ok(files)
    }
}
